package preprocessing;
import featureide.fm.generator.ModelGenerator;
import featureide.fm.model.Constraint;
import featureide.fm.model.Feature;
import featureide.fm.model.FeatureModel;

import java.util.*;

public class testFMTransform {
	public static final double ratio = 0.2;
	
	private FeatureModel featureModel = new FeatureModel();
	private Random ran = new Random();
	private Map<Feature,Boolean> choices = new HashMap<Feature,Boolean>();
	private Map<Feature,Boolean> definites = new HashMap<Feature,Boolean>();
	private Map<Feature,Boolean> excludes = new HashMap<Feature,Boolean>();
	
	public testFMTransform(FeatureModel fm){
		featureModel = fm;
	}
	
	public void refresh(){
		choices.clear();
		definites.clear();
		excludes.clear();
	}
	
	public void setFeatureModel(FeatureModel fm){
		featureModel = fm;
	}
	public FeatureModel getFeatureModel(){
		return featureModel;
	}
	
	
	public Map<Feature,Boolean> generate(){
		refresh();
		decide(featureModel.getRoot());
		for(Feature choice:choices.keySet()){
			if(!definites.containsKey(choice) && !excludes.containsKey(choice)){
				addToDefinites(choice);
			}
		}
		finalCheck();
		return definites;
	}
	
	public Map<Feature,Boolean> generate(LinkedList<Feature> choices){
		definites.clear();
		excludes.clear();
		for(Feature choice:choices){
			if(!definites.containsKey(choice) && !excludes.containsKey(choice)){
				addToDefinites(choice);
			}
		}
		finalCheck();
		return definites;
	}
	
	public void generate(ArrayList c){
		for(Feature feature:featureModel.getFeatures()){
			if(c.contains(feature.getName().toString()) )
				choices.put(feature, false);
		}
		for(Feature choice:choices.keySet()){
			if(!definites.containsKey(choice) && !excludes.containsKey(choice)){
				addToDefinites(choice);
			}
		}
		finalCheck();
	}
	
	public boolean[] generate(boolean[] c){
		refresh();
		boolean[] rc = new boolean[c.length];
		int i=0;
		for(Feature feature:featureModel.getFeatures()){
			if(c[i])
				choices.put(feature,false);
			i++;
		}
		for(Feature choice:choices.keySet()){
			if(!definites.containsKey(choice) && !excludes.containsKey(choice)){
				addToDefinites(choice);
			}
		}

		i=0;
		for(Feature feature:featureModel.getFeatures()){
			if(definites.containsKey(feature))
				rc[i] = true;
			else
				rc[i] = false;
			i++;
		}
		finalCheck();
		return rc;
	}
	
	public Map<Feature,Boolean> addChoice(int posting){
		for(Feature feature:featureModel.getFeatures()){
			if(posting == 0 && !definites.containsKey(feature) && !excludes.containsKey(feature))
				addToDefinites(feature);
			posting--;
		}
		finalCheck();
		return definites;
	}
	
	public boolean checkTotalResource(int totalResource){
		boolean fit;
		int sum = 0;
		for(Feature feature:definites.keySet()){
			sum += feature.getCost();
		}
		if(sum < totalResource)
			fit = true;
		else
			fit = false;
		return fit;
	}
	
	private void decide(Feature feature){
		for(Feature child:feature.getChildren()){
			decide(child);
		}
		if(!feature.isAbstract() && ran.nextDouble() < ratio){
			choices.put(feature,false);
		}
	}
	
	
	private void addToDefinites(Feature feature){
		if(!definites.containsKey(feature) && !excludes.containsKey(feature)){
			LinkedList<Constraint> constraints = new LinkedList<Constraint>();
			definites.put(feature,false);
			if(feature.getParent() != null && !definites.containsKey(feature.getParent())){
				addToDefinites(feature.getParent());				
			}
			if(feature.getParent() != null && feature.getParent().isAlternative()){
				for(Feature brother:feature.getParent().getChildren())
					addToExcludes(brother);
			}
			if(feature.isAnd() && feature.getChildrenCount()!=0){
				for(Feature child:feature.getChildren()){
					if(child.isMandatory())
						addToDefinites(child);
				}
			}
			constraints = getConstraints(feature);
			if(constraints.size() != 0){
				for(Constraint constraint:constraints){
					if(constraint.getConstraintType() == Constraint.ConstraintType.exclude){
						if(feature.getName().equals(constraint.getFeatureA()) ){
							addToExcludes(find(constraint.getFeatureB()));
						}
						else{
							addToExcludes(find(constraint.getFeatureA()));
						}
					}
					else{
						if(feature.getName().equals(constraint.getFeatureA())){
							addToDefinites(find(constraint.getFeatureB()));
						}
					}
				}
			}
		}
	}
	private void addToExcludes(Feature feature){
		if(!definites.containsKey(feature) && !excludes.containsKey(feature)){
			LinkedList<Constraint> constraints = new LinkedList<Constraint>();
			excludes.put(feature,false);
			if(feature.getParent()!=null && feature.getParent().isAnd() && feature.isMandatory()){
				addToExcludes(feature.getParent());
			}
			if(feature.getChildrenCount() != 0){
				for(Feature child:feature.getChildren()){
					addToExcludes(child);
				}
			}
			constraints = getConstraints(feature);
			if(constraints.size() != 0){
				for(Constraint constraint:constraints){
					if(constraint.getConstraintType() == Constraint.ConstraintType.require){
						if(feature.getName().equals(constraint.getFeatureB())){
							addToExcludes(find(constraint.getFeatureA()));
						}
					}
				}
			}
		}		
	}
	
	private LinkedList<Constraint> getConstraints(Feature feature){
		LinkedList<Constraint> constraints = new LinkedList<Constraint>();
		for(Constraint constraint:featureModel.getConstraints()){
			if(feature.getName().equals(constraint.getFeatureA()) || feature.getName().equals(constraint.getFeatureB())){
				constraints.add(constraint);
			}
		}
		return constraints;
	}
	
	private Feature find(String name){
		for(Feature feature:featureModel.getFeatures()){
			if(feature.getName().equals(name))
				return feature;
		}
		return null;
	}
	
	
	public boolean check(int posting){
		for(Feature feature:featureModel.getFeatures()){
			if(posting == 0){
				if(definites.containsKey(feature)||excludes.containsKey(feature))
					return false;
			}
			posting--;
		}
		return true;
	}
	
	private void finalCheck(){
		int flag;
		Map<Feature,Boolean> grows = new HashMap<Feature,Boolean>();		
		for(Feature feature:definites.keySet()){
			if(feature.hasChildren()){
				flag = 0;
				for(Feature child:feature.getChildren()){
					System.out.println(child.getName());
					if(definites.containsKey(child)){
						flag = 1;
						break;
					}					
				}
				if(flag == 0)
					grows.put(feature, true);
//					addToDefinites(feature.getChildren().get(ran.nextInt(feature.getChildrenCount())));
			}
		}
		for(Feature grow:grows.keySet()){
			Feature child = grow.getChildren().get(ran.nextInt(grow.getChildrenCount()));
			while(!child.isConcrete()){
				addToDefinites(child);
				child = child.getChildren().get(ran.nextInt(child.getChildrenCount()));
			}
			addToDefinites(child);
		}
	}
	
	public int getTotalValue(){
		int totalValue = 0;
		for(Feature feature:definites.keySet()){
			totalValue += feature.getValue();
		}
		return totalValue;
	}
	
	public int getTotalResource(){
		int sum = 0;
		for(Feature feature:definites.keySet()){
			sum += feature.getCost();
		}
		return sum;
	}
	
	public double getRatio(){
		double ratio = 0;
		int values=0,costs=0;
		for(Feature feature:definites.keySet()){
			values += feature.getValue();
			costs += feature.getCost();
		}
		ratio = values/costs;
		return ratio;
	}	
	
	public void printAll(){
		System.out.println("all:");
		for(Feature feature:featureModel.getFeatures()){
			System.out.println(feature.getName()+"   "+feature.getValue()+"   "+feature.getCost());
		}
		System.out.println();
	}
	
	public void printChoices(){
		System.out.println("choices:");
		for(Feature feature:choices.keySet()){
			System.out.print(feature.getName()+"   ");
		}
		System.out.println();
	}
	public void printDefinites(){
		System.out.println("definites:");
		for(Feature feature:definites.keySet()){
			System.out.print(feature.getName()+"   ");
		}
		System.out.println();
	}
	public void printExcludes(){
		System.out.println("excludes:");
		for(Feature feature:excludes.keySet()){
			System.out.print(feature.getName()+"   ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		ModelGenerator mg = new ModelGenerator();
		FeatureModel fm = mg.loadFeatureModel(".\\model.m");
		
		ArrayList<String> firstchoices = new ArrayList<String>();
//		firstchoices.add("a9");
		firstchoices.add("a3");			
//		firstchoices.add("a8");		
	

		testFMTransform format = new testFMTransform(fm);
		format.setFeatureModel(fm);
		format.generate(firstchoices);
		format.printChoices();
		format.printDefinites();
		format.printExcludes();
	}
}
